package com.example.animationdrawabledemo;

import java.util.ArrayDeque;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Process;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Gravity;

/**
 * 
 * @author cainli 2013-6-9
 */
@SuppressLint("NewApi")
public class BubbleDrawable extends Drawable implements Runnable, Animatable {
	private final BubbleDrawableState mBubbleDrawableState;
	private static final String TAG = BubbleDrawable.class.getSimpleName();
	private static final int DEFAULT_PAINT_FLAGS = Paint.FILTER_BITMAP_FLAG
			| Paint.DITHER_FLAG;
	private Paint mPaint = new Paint(DEFAULT_PAINT_FLAGS);
	private int mCurFrame = -1;
	private int count = 10;
	private boolean mOneShot = false;
	private Bitmap currBitmap;
	private Bitmap firstBitmap;
	private Bitmap nextBitmap;
	private int nextFrame = 0;
	private boolean mDecodeNextFrameEnd = true;
	private long duration = 50;
	private Context mContext;
	private Rect drawableRect;
	private WorkerRunnable mRunnable;
	private int mDensity;
	public BubbleDrawable(Context context) {
		loadNextBitmap(false, true);
		mContext = context;
		mDensity = context.getResources().getDisplayMetrics().densityDpi;
		mBubbleDrawableState = new BubbleDrawableState(mRunnable);
	}

	public BubbleDrawable(BubbleDrawableState state, Resources res) {
		mBubbleDrawableState = state;
		if (res != null) {
			mDensity = res.getDisplayMetrics().densityDpi;
		} else {
			mDensity = state.mTargetDensity;
		}
		setmRunnable(state != null ? state.mRunnable : null);
	}

	public void setmRunnable(WorkerRunnable mRunnable) {
		this.mRunnable = mRunnable;
	}


	@Override
	public boolean setVisible(boolean visible, boolean restart) {
		boolean changed = super.setVisible(visible, restart);
		if (visible) {
			if (changed || restart) {
				loadNextBitmap(true, true);
			}
		} else {
			unscheduleSelf(this);
		}
		return changed;
	}

	/**
	 * <p>
	 * Starts the animation, looping if necessary. This method has no effect if
	 * the animation is running. Do not call this in the
	 * {@link android.app.Activity#onCreate} method of your activity, because
	 * the {@link android.graphics.drawable.AnimationDrawable} is not yet fully
	 * attached to the window. If you want to play the animation immediately,
	 * without requiring interaction, then you might want to call it from the
	 * {@link android.app.Activity#onWindowFocusChanged} method in your
	 * activity, which will get called when Android brings your window into
	 * focus.
	 * </p>
	 * 
	 * @see #isRunning()
	 * @see #stop()
	 */
	public void start() {
		if (!isRunning()) {
			run();
		}
	}

	/**
	 * <p>
	 * Stops the animation. This method has no effect if the animation is not
	 * running.
	 * </p>
	 * 
	 * @see #isRunning()
	 * @see #start()
	 */
	public void stop() {
		if (isRunning()) {
			unscheduleSelf(this);
		}
	}

	/**
	 * <p>
	 * Indicates whether the animation is currently running or not.
	 * </p>
	 * 
	 * @return true if the animation is running, false otherwise
	 */
	public boolean isRunning() {
		return mCurFrame > -1;
	}

	/**
	 * <p>
	 * This method exists for implementation purpose only and should not be
	 * called directly. Invoke {@link #start()} instead.
	 * </p>
	 * 
	 * @see #start()
	 */
	public void run() {
		currBitmap = nextBitmap;
		mCurFrame = nextFrame;
		invalidateSelf();
	}

	@Override
	public void unscheduleSelf(Runnable what) {
		mCurFrame = -1;
		super.unscheduleSelf(what);
	}

	@Override
	public void draw(Canvas canvas) {
//		Paint paint = new Paint();
//		paint.setColor(Color.RED);
//		paint.setTextSize(24);
//		canvas.save();
//		canvas.drawText("first bitmap :",50, 50, paint);
//		if (firstBitmap != null) {
//			drawableRect = new Rect(0, 0, firstBitmap.getWidth(),
//					firstBitmap.getHeight());
//			canvas.drawBitmap(firstBitmap, drawableRect, drawableRect, null);
//		}
//		canvas.restore();
//		canvas.save();
//		canvas.translate(0, 100);
//		canvas.drawText("mCurFrame :" + mCurFrame, 50, 50, paint);
//		if (currBitmap != null) {
//			drawableRect = new Rect(0,0, currBitmap.getWidth(),
//					currBitmap.getHeight());
//			canvas.drawBitmap(currBitmap, drawableRect, drawableRect, null);
//		}
//		canvas.restore();
//		canvas.save();
//		canvas.translate(0, 200);
//		canvas.drawText("next bitmap :",50, 50, paint);
//		if (nextBitmap != null) {
//			drawableRect = new Rect(0, 0, nextBitmap.getWidth(),
//					nextBitmap.getHeight());
//			canvas.drawBitmap(nextBitmap, drawableRect, drawableRect, null);
//		}
//		canvas.restore();
		if (currBitmap != null) {
			drawableRect = new Rect(0, 0, currBitmap.getWidth(),currBitmap.getHeight());
			canvas.drawBitmap(currBitmap, drawableRect, getBounds(),mBubbleDrawableState.mPaint);
		}
		loadNextBitmap(false, true);
	}

	@Override
	public int getOpacity() {
		return PixelFormat.TRANSPARENT;
	}

	@Override
	public void setAlpha(int alpha) {
		mPaint.setAlpha(alpha);
	}

	@Override
	public void setColorFilter(ColorFilter cf) {
		mPaint.setColorFilter(cf);
	}

	@Override
	public void setFilterBitmap(boolean filter) {
		mPaint.setFilterBitmap(filter);
	}

	@Override
	public void setDither(boolean dither) {
		mPaint.setDither(dither);
	}

	public static final Executor THREAD_POOL_EXECUTOR = Executors
			.newSingleThreadExecutor();
	private static volatile SerialExecutor executor = new SerialExecutor();;
	private FutureTask<Bitmap> mFuture;

	private static class SerialExecutor implements Executor {
		final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
		Runnable mActive;

		public synchronized void execute(final Runnable r) {
			mTasks.offer(new Runnable() {
				public void run() {
					try {
						r.run();
					} finally {
						scheduleNext();
					}
				}
			});
			if (mActive == null) {
				scheduleNext();
			}
		}

		protected synchronized void scheduleNext() {
			if ((mActive = mTasks.poll()) != null) {
				THREAD_POOL_EXECUTOR.execute(mActive);
			}
		}

		protected synchronized void clear() {
			mTasks.clear();
		}

		protected synchronized int taskCount() {
			return mTasks.size();
		}
	}

	private void loadNextBitmap(boolean unschedule,boolean animate) {
		int frame = mCurFrame + 1;
		if (frame >= count) {
			frame = 0;
		}
		if (unschedule) {
			unscheduleSelf(this);
		}
		if ((!mOneShot || frame < (count - 1)) && mDecodeNextFrameEnd && animate) {
			mDecodeNextFrameEnd = false;
			long nextFrameTime = android.os.SystemClock.uptimeMillis()+ duration;
			mRunnable = new WorkerRunnable(frame,nextFrameTime);
			mFuture = new FutureTask<Bitmap>(mRunnable);
			executor.execute(mFuture);
		}
	}
	
	private Handler sHandler = new Handler();
	private class WorkerRunnable implements Callable{
		private int frame;
		private long drawTime;
		public WorkerRunnable(int frame, long drawTime) {
			super();
			this.frame = frame;
			this.drawTime = drawTime;
		}
		@Override
		public Object call() throws Exception {
			Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
			Log.v(TAG, ""+frame);
			nextFrame = frame;
			nextBitmap = getBitmapFromFile(frame);
			if(firstBitmap == null && frame == 0){
				firstBitmap = nextBitmap;
			}
			mDecodeNextFrameEnd = true;
			long now = android.os.SystemClock.uptimeMillis();
			if (now < drawTime) {
				sHandler.postDelayed(BubbleDrawable.this, drawTime - now);
			}else {
				sHandler.post(BubbleDrawable.this);
			}
			return nextBitmap;
		}
		private Bitmap getBitmapFromFile(final int frame) {
			Bitmap bitmap = BitmapFactory.decodeResource(
					mContext.getResources(), getResId(frame));
			return bitmap;
		}
	}
	
	
	public void cancle() {
		executor.clear();
		mFuture.cancel(true);
	}
	
	@Override
	public int getChangingConfigurations() {
		return super.getChangingConfigurations() | mBubbleDrawableState.mChangingConfigurations;
	}
	
	
	final static class BubbleDrawableState extends ConstantState {
		WorkerRunnable mRunnable;
		int mChangingConfigurations;
		int mGravity = Gravity.FILL;
		int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
		Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);

		public BubbleDrawableState(WorkerRunnable runnable) {
			this.mRunnable = runnable;
		}

		public BubbleDrawableState(BubbleDrawableState state) {
			this(state.mRunnable);
			mChangingConfigurations = state.mChangingConfigurations;
			mGravity = state.mGravity;
			mTargetDensity = state.mTargetDensity;
		}

		@Override
		public int getChangingConfigurations() {
			return mChangingConfigurations;
		}

		@Override
		public Drawable newDrawable() {
			return new BubbleDrawable(this, null);
		}

		@Override
		public Drawable newDrawable(Resources res) {
			return new BubbleDrawable(this, res);
		}
	}

	private int getResId(int frame) {
		int resId = R.drawable.gif_2_0001;
		switch (frame) {
		case 0:
			resId = R.drawable.gif_2_0001;
			break;
		case 1:
			resId = R.drawable.gif_2_0002;
			break;
		case 2:
			resId = R.drawable.gif_2_0003;
			break;
		case 3:
			resId = R.drawable.gif_2_0004;
			break;
		case 4:
			resId = R.drawable.gif_2_0005;
			break;
		case 5:
			resId = R.drawable.gif_2_0006;
			break;
		case 6:
			resId = R.drawable.gif_2_0007;
			break;
		case 7:
			resId = R.drawable.gif_2_0008;
			break;
		case 8:
			resId = R.drawable.gif_2_0009;
			break;
		case 9:
			resId = R.drawable.gif_2_0010;
			break;
		default:
			break;
		}
		return resId;
	}

}
